package tman.system.peer.tman;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Random;

import se.sics.asdistances.ASDistances;
import se.sics.kompics.ComponentDefinition;
import se.sics.kompics.Handler;
import se.sics.kompics.Negative;
import se.sics.kompics.Positive;
import se.sics.kompics.network.Network;
import se.sics.kompics.timer.SchedulePeriodicTimeout;
import se.sics.kompics.timer.Timer;
import tman.simulator.snapshot.Snapshot;

import common.configuration.Global;
import common.configuration.TManConfiguration;
import common.events.PeerDataRequest;
import common.events.PeerDataResponse;
import common.peer.PeerAddress;
import common.peer.PeerInfo;
import common.simulation.PeerStatus;

import cyclon.system.peer.cyclon.CyclonPartnersPort;
import cyclon.system.peer.cyclon.CyclonPartnersRequest;
import cyclon.system.peer.cyclon.CyclonPartnersResponse;

public final class TMan_Archive extends ComponentDefinition {
        Negative<PeerStatus> peerStatusPort = negative(PeerStatus.class);
        Positive<CyclonPartnersPort> cyclonPartnersPort = positive(CyclonPartnersPort.class);
        Positive<Network> networkPort = positive(Network.class);
        Positive<Timer> timerPort = positive(Timer.class);
        private long period;
        private PeerInfo self;
        private ArrayList<PeerAddress> candidates;
        private ArrayList<PeerInfo> parents;
        private ArrayList<PeerInfo> children;
        private ArrayList<PeerInfo> tmanPartners;
        private ArrayList<PeerInfo> cyclonPartners;
        private TManConfiguration tmanConfiguration;
        private HashMap<BigInteger, Integer> parentPreferences;
        private Integer ageLimit;
        private Integer limit;
        private boolean cyclonOnly;
        private HashMap<String, Integer> eventCounts;

        private static final Random RAND = new Random(Global.seed);
        private static final int VIEW_MAX_SIZE = Global.tvs;


        //-------------------------------------------------------------------  
        public TMan_Archive() {
        	/////////////////
        	cyclonOnly = false;
        	////////////////
        		candidates = new ArrayList<PeerAddress>();
                tmanPartners = new ArrayList<PeerInfo>();
                cyclonPartners = new ArrayList<PeerInfo>();
                parents = new ArrayList<PeerInfo>();
                children = new ArrayList<PeerInfo>();
                parentPreferences = new HashMap<BigInteger, Integer>();
                limit = 0;
                eventCounts = new HashMap<String, Integer>();
                
                //Age control parameter
                ageLimit = 15;
                
                subscribe(handleInit, control);
                subscribe(handleRound, timerPort);
                subscribe(handleCyclonPartnersResponse, cyclonPartnersPort);
                subscribe(handleTManBufferRequest, networkPort);
                subscribe(handleTManBufferResponse, networkPort);
                subscribe(handleTManChildResponse, networkPort);
                subscribe(handleTManAcquireChild, networkPort);
                subscribe(handleTManOrphanChild, networkPort);
                subscribe(handleTManOrphanSelf, networkPort);
                subscribe(handleTManHopCountChange, networkPort);
                subscribe(handlePeerDataRequest, peerStatusPort);
                
        }

        //-------------------------------------------------------------------  
        Handler<TManInit> handleInit = new Handler<TManInit>() {
                public void handle(TManInit init) {
                	    self = new PeerInfo(init.getSelf(), new ArrayList<PeerAddress>());
                	    self.toggleTest(); // USED FOR TESTING
                	    limit = self.getPeerAddress().getBandwidth();
                        tmanConfiguration = init.getConfiguration();
                        period = tmanConfiguration.getPeriod();

                        SchedulePeriodicTimeout rst = new SchedulePeriodicTimeout(period, period);
                        rst.setTimeoutEvent(new TManSchedule(rst));
                        trigger(rst, timerPort);

                }
        };

        //-------------------------------------------------------------------  
        Handler<TManSchedule> handleRound = new Handler<TManSchedule>() {
                public void handle(TManSchedule event) {
                        CyclonPartnersRequest response = new CyclonPartnersRequest();
                        response.setSelf(self);
                        trigger(response, cyclonPartnersPort);
                }
        };
        
        //-------------------------------------------------------------------Edited
        Handler<PeerDataRequest> handlePeerDataRequest = new Handler<PeerDataRequest>() {
        	public void handle(PeerDataRequest event) {
        		PeerData pd = new PeerData(self.getPeerAddress(), convertToPeerAddress(parents), convertToPeerAddress(children), convertToPeerAddress(cyclonPartners), convertToPeerAddress(tmanPartners), averageCyclonAge(), averageTmanAge(), self.getHopCount());
        		pd.setEventCounts(eventCounts);

        		for(String eventType: eventCounts.keySet())
        		{
        			eventCounts.put(eventType, 0);
        		}
        		PeerDataResponse responseEvent = new PeerDataResponse(event, pd);
        		trigger(responseEvent, peerStatusPort);
        	}
        };


        //-------------------------------------------------------------------  
        Handler<CyclonPartnersResponse> handleCyclonPartnersResponse = new Handler<CyclonPartnersResponse>() {
                public void handle(CyclonPartnersResponse event) {
                		//Increment Age of TMan Partners
                		incrementAge(tmanPartners);
                		//System.out.println(self.getAge());
                		cyclonPartners = event.getPartnersInfo();
                        cyclonPartners = removeSelf(cyclonPartners);
                        if(checkForTestVar(cyclonPartners))
                        	System.out.println("Cloning failure detected! #1");

                        List<PeerInfo> buffer;
                        if(tmanPartners.size() > 0) {
                                PeerInfo q = rank(self, tmanPartners, "children").get(RAND.nextInt(tmanPartners.size())/2);
                                buffer = merge(tmanPartners, cyclonPartners);
                                if(!cyclonOnly)
                                {
	                                buffer = merge(self, buffer);
	                                ArrayList<PeerInfo> newbuffer = copyAllPI((ArrayList<PeerInfo>) buffer);
	                                TManBufferRequest request = new TManBufferRequest(self.getPeerAddress(), q.getPeerAddress(), newbuffer);
	                                trigger(request, networkPort);
                                }
                                else
                                {
                                	//Add a new copy of the buffer
                                	acquireChildren((ArrayList<PeerInfo>)buffer);
                                }
                        } else {
                                tmanPartners = (ArrayList<PeerInfo>) selectView(self, cyclonPartners);
                        }

                }
        };

        
        Handler<TManBufferRequest> handleTManBufferRequest = new Handler<TManBufferRequest>() {

            @Override
            public void handle(TManBufferRequest event) {
                    ArrayList<PeerInfo> bufferP = (ArrayList<PeerInfo>) event.getBuffer();
                    if(checkForTestVar(bufferP))
                    	System.out.println("Cloning failure detected! #2"); //USED FOR TESTING PURPOSES
                    bufferP = removeSelf(bufferP);
                    bufferP = removeChildren(bufferP);
                    acquireChildren(bufferP);
                    ArrayList<PeerInfo> buffer = (ArrayList<PeerInfo>) merge (self, tmanPartners);
                    ArrayList<PeerInfo> newBuffer = copyAllPI(buffer);
                    TManBufferResponse response = new TManBufferResponse(self.getPeerAddress(), event.getPeerSource(), newBuffer);
                    trigger(response, networkPort);
            }

    };
    
    Handler<TManBufferResponse> handleTManBufferResponse = new Handler<TManBufferResponse>() {

        @Override
        public void handle(TManBufferResponse event) {
                // Constructing a ring topology
        		ArrayList<PeerInfo> bufferP = (ArrayList<PeerInfo>)event.getBuffer();
                if(checkForTestVar(bufferP))
                	System.out.println("Cloning failure detected! #3"); // USED FOR TESTING PURPOSES
        		bufferP = removeSelf(bufferP);
        		bufferP = removeChildren(bufferP);
        		//
        		acquireChildren(bufferP);
        }


};



        Handler<TManAcquireParent> handleTManAcquireChild = new Handler<TManAcquireParent>() {

            @Override
            public void handle(TManAcquireParent event) {
         
            PeerInfo parent = event.getParent();
            if(self.isChild(parent) || self.getPeerAddress().isSource())
            {
            	//Refuse parenting request if he's your child or you're a source node -- NOT SURE ABOUT IMPLICATIONS
            	PeerInfo newSelf = copyPI(self);
            	TManParentResponse response = new TManParentResponse(self.getPeerAddress(), parent.getPeerAddress(), newSelf, false);
            	trigger(response, networkPort);
            	return;
            }
            if(parents.size() < Global.id)
            {
            	if(parent.getPeerAddress().getBandwidth()> self.getPeerAddress().getBandwidth()||(parent.getPeerAddress().getBandwidth() == self.getPeerAddress().getBandwidth() && parent.getHopCount() <= self.getHopCount()))
            	{
	            	parents.add(parent);
	            	self.addParent(parent.getPeerAddress(), parent.getHopCount());
	            	//Snapshot.updateParents(self.getPeerAddress(), convertToPeerAddress(parents));
	            	updateHopCount();
	            	PeerInfo newSelf = copyPI(self);
	            	TManParentResponse response = new TManParentResponse(self.getPeerAddress(), parent.getPeerAddress(), newSelf, true);
	            	trigger(response, networkPort);
	            	return;
            	}
            	else
            	{
            		PeerInfo newSelf = copyPI(self);
                	TManParentResponse response = new TManParentResponse(self.getPeerAddress(), parent.getPeerAddress(), newSelf, false);
                	trigger(response, networkPort);
                	return;
            	}
            }
            else
            {
            	parents.add(parent);
            	parents = (ArrayList<PeerInfo>) rank(self, parents, "parents");
            	if(parents.get(parents.size()-1).getPeerAddress() == parent.getPeerAddress())
            	{
            		//Reject parent based on its diminished favorability
            		parents.remove(parents.size()-1);
                	PeerInfo newSelf = copyPI(self);
                	TManParentResponse response = new TManParentResponse(self.getPeerAddress(), parent.getPeerAddress(), newSelf, false);
                	trigger(response, networkPort);
                	return;	
            	}
            	else
            	{
            		//Accept request, and remove least favored parent
        			PeerInfo exParent = parents.get(parents.size()-1);
            		self.removeParent(exParent.getPeerAddress());
            		parents.remove(parents.size()-1);
            		self.addParent(parent.getPeerAddress(), parent.getHopCount());
            		updateHopCount();
            		PeerInfo newSelf = copyPI(self);
            		PeerInfo newSelf2 = copyPI(self);
            		TManParentResponse response = new TManParentResponse(self.getPeerAddress(), parent.getPeerAddress(), newSelf, true);
            		TManOrphanSelf notify = new TManOrphanSelf(self.getPeerAddress(), exParent.getPeerAddress(), newSelf2);
            		if(eventCounts.get("TManOrphanSelf")==null)
            			eventCounts.put("TManOrphanSelf", 1);
            		else eventCounts.put("TManOrphanSelf", eventCounts.get("TManOrphanSelf")+1);
            		//Snapshot.updateParents(self.getPeerAddress(), convertToPeerAddress(parents));
                	trigger(response, networkPort);
                	trigger(notify, networkPort);
                	return;
            	}
//            	for(int i=parents.size()-1; i>=0; i--)
//            	{
//            		//We also need to remove the LEAST favorable parent -- might result in faster convergence
//            		if (parent.getPeerAddress().getBandwidth() > parents.get(i).getPeerAddress().getBandwidth() || (parent.getHopCount() < parents.get(i).getHopCount() && parent.getPeerAddress().getBandwidth() == parents.get(i).getPeerAddress().getBandwidth()))
//            		{
//            			PeerInfo exParent = parents.get(i);
//	            		self.removeParent(parents.get(i).getPeerAddress());
//	            		parents.remove(i); //re-sort parent list
//	            		parents.add(parent);
//	            		self.addParent(parent.getPeerAddress());
//	            		updateHopCount();
//	            		PeerInfo newSelf = copyPI(self);
//	            		PeerInfo newSelf2 = copyPI(self);
//	            		TManChildResponse response = new TManChildResponse(self.getPeerAddress(), parent.getPeerAddress(), newSelf, true);
//	            		TManOrphanSelf notify = new TManOrphanSelf(self.getPeerAddress(), exParent.getPeerAddress(), newSelf2);
//	            		//Snapshot.updateParents(self.getPeerAddress(), convertToPeerAddress(parents));
//	                	trigger(response, networkPort);
//	                	trigger(notify, networkPort);
//	                	return;
//            		}
//            	}
//            	PeerInfo newSelf = copyPI(self);
//            	TManChildResponse response = new TManChildResponse(self.getPeerAddress(), parent.getPeerAddress(), newSelf, false);
//            	trigger(response, networkPort);
//            	return;	
            }
            }
        };
        
        Handler<TManParentResponse> handleTManChildResponse = new Handler<TManParentResponse>() {

            @Override
            public void handle(TManParentResponse event) {
            	limit++;
            	candidates.remove(event.getChildInfo().getPeerAddress());
            	if(event.isAccepting())
            	{
            		children.add(event.getChildInfo());
            		children = (ArrayList<PeerInfo>) rank(self, children, "children");

            		if(children.size() > self.getPeerAddress().getBandwidth())
            		{ 
            			PeerInfo newSelf = copyPI(self);
            			TManOrphanChild notify = new TManOrphanChild(self.getPeerAddress(), children.get(children.size()-1).getPeerAddress(), newSelf);
            			trigger(notify, networkPort);
            			children.remove(children.size() -1);
                		if(eventCounts.get("TManOrphanChild")==null)
                			eventCounts.put("TManOrphanChild", 1);
                		else eventCounts.put("TManOrphanChild", eventCounts.get("TManOrphanChild")+1);
            		}

            		//Snapshot.updateChildren(self.getPeerAddress(), convertToPeerAddress(children));
            	}
            	
            	for(int i=0; i<tmanPartners.size();i++)
            	{
            		if(tmanPartners.get(i).getPeerAddress().getPeerId() == event.getChildInfo().getPeerAddress().getPeerId())
            		{
            			tmanPartners.remove(i);
            			//Snapshot.updateTManPartners(self.getPeerAddress(), convertToPeerAddress(tmanPartners));
            			//tmanPartners.add(event.getChildInfo());
            			return;
            		}
            	}
            }
        };
        
        Handler<TManOrphanSelf> handleTManOrphanSelf = new Handler<TManOrphanSelf>() {
        	
        	@Override
        	public void handle(TManOrphanSelf event) {
        		for(int i=0; i<children.size(); i++)
        		{
        			if(children.get(i).getPeerAddress().getPeerId() == event.getChildInfo().getPeerAddress().getPeerId())
        			{
        				children.remove(i);
        				//you might want to remove this from tmanpartners as well; debatable
        				//Snapshot.updateChildren(self.getPeerAddress(), convertToPeerAddress(children));
        				return;
        			}
        		}
        	}
        };
        
        Handler<TManOrphanChild> handleTManOrphanChild = new Handler<TManOrphanChild>() {
        	
        	@Override
        	public void handle(TManOrphanChild event) {
        		for(int i=0; i<parents.size(); i++)
        		{
        			if(parents.get(i).getPeerAddress().getPeerId() == event.getExParentInfo().getPeerAddress().getPeerId())
        			{
        				self.removeParent(parents.get(i).getPeerAddress());
        				parents.remove(i);
        				updateHopCount();
        				//Snapshot.updateParents(self.getPeerAddress(), convertToPeerAddress(parents));
        				return;
        			}
        		}
        	}
        };
        
        Handler<TManUpdateCost> handleTManHopCountChange = new Handler<TManUpdateCost>() {
        	
        	@Override
        	public void handle(TManUpdateCost event) {
        		if (event.isParent() == true)
        		{
        			for(int i=0; i<parents.size(); i++)
        			{
        				if(event.getSenderInfo().getPeerAddress().getPeerId() == parents.get(i).getPeerAddress().getPeerId())
        				{
    	            		self.removeParent(parents.get(i).getPeerAddress());
    	            		parents.remove(i); //re-sort parent list
    	            		parents.add(event.getSenderInfo());
    	            		self.addParent(event.getSenderInfo().getPeerAddress(), event.getSenderInfo().getHopCount());
    	            		//Snapshot.updateParents(self.getPeerAddress(), convertToPeerAddress(parents));
    	            		updateHopCount();
        				}
        			}
        		}
        		
        		else
        		{
        			for(int i=0; i<children.size(); i++)
        			{
        				if(event.getSenderInfo().getPeerAddress().getPeerId() == children.get(i).getPeerAddress().getPeerId())
        				{
    	            		children.remove(i);
    	            		children.add(event.getSenderInfo());
    	            		//Snapshot.updateChildren(self.getPeerAddress(), convertToPeerAddress(children));
        				}
        			}
        		}
        		
        	}
        };
//Functions

        private static List<PeerInfo> rank(PeerInfo descriptor, List<PeerInfo> view, String type) {
                List<PeerInfo> tmp = quickSort(descriptor, view, 0, view.size()-1, type);
                //System.out.println(descriptor + ": " + tmp);
               return tmp;
        }


        private static List<PeerInfo> quickSort(PeerInfo descriptor, List<PeerInfo> tab, int first, int last, String type) {
                if(first < last) {
                        int pivotIndex = partition(descriptor, tab, first, last, first, type);
                        tab = quickSort(descriptor, tab, first, pivotIndex-1, type);
                        tab = quickSort(descriptor, tab, pivotIndex+1, last, type);
                }
                return tab;
        }

        private static int partition(PeerInfo descriptor, List<PeerInfo> tab, int first, int last, int pivotIndex, String type) {
                PeerInfo pivot = tab.get(pivotIndex);
                tab.set(pivotIndex, tab.get(last));
                tab.set(last, pivot);
                int j = first;
                if(type == "children")
                {
	                for(int i = first; i < last; i++) {
	                	if(descriptor.favoredChild(tab.get(i), tab.get(last))) {
	                                PeerInfo tmp = tab.get(i);
	                                tab.set(i, tab.get(j));
	                                tab.set(j, tmp);
	                                j++;
	                        }
	                }
                }
                else if(type == "parents")
                {
	                for(int i = first; i < last; i++) {
	                	if(descriptor.favoredParent(tab.get(i), tab.get(last))) {
	                                PeerInfo tmp = tab.get(i);
	                                tab.set(i, tab.get(j));
	                                tab.set(j, tmp);
	                                j++;
	                        }
	                }
                }
                PeerInfo tmp = tab.get(last);
                tab.set(last, tab.get(j));
                tab.set(j, tmp);
                return j;
        }

        private static List<PeerInfo> merge(PeerInfo a, List<PeerInfo> list) {
                List<PeerInfo> r = new ArrayList<PeerInfo>(list);
                boolean check = false;
                //if(r.indexOf(a) == -1) {
                //        r.add(a);
                // }
                for(int i=0; i<list.size(); i++)
                {
                	if(a.getPeerAddress().getPeerId() == list.get(i).getPeerAddress().getPeerId())
                	{
                		check = true;
                		if(a.getAge()<list.get(i).getAge())
                		{
                			r.set(i, a);
                		}
                		break;
                	}
                }
                if(check == false)
                	r.add(a);
                return r;
        }

        private static List<PeerInfo> merge(List<PeerInfo> a, List<PeerInfo> b) {
                List<PeerInfo> r = new ArrayList<PeerInfo>(b);
               // for(PeerInfo p : b) {
               //         if(r.indexOf(p) == -1) {
               //                 r.add(p);
               //         }
               // }
                boolean check = false;
                for(int i=0; i<a.size(); i++)
                {
                	for(int j=0; j<b.size(); j++)
                	{
                		if(a.get(i).getPeerAddress().getPeerId() == b.get(j).getPeerAddress().getPeerId())
                		{
                			if(a.get(i).getAge() < b.get(j).getAge())
                				r.set(j, a.get(i));
                			check = true;
                			break;
                		}
                	}
                	if(check == false)
                		r.add(a.get(i));
                	check = false;
                }
                return r;
        }
        
        private static List<PeerAddress> merge2(PeerAddress a, List<PeerAddress> list) {
            List<PeerAddress> r = new ArrayList<PeerAddress>(list);
            if(r.indexOf(a) == -1) {
                    r.add(a);
            }
            return r;
    }

    private static List<PeerAddress> merge2(List<PeerAddress> a, List<PeerAddress> b) {
            List<PeerAddress> r = new ArrayList<PeerAddress>(a);
            for(PeerAddress p : b) {
                    if(r.indexOf(p) == -1) {
                            r.add(p);
                    }
            }
            return r;
    }

        private static List<PeerInfo> selectView(PeerInfo descriptor, List<PeerInfo> list) {
                List<PeerInfo> r = new ArrayList<PeerInfo>(list);
                int i = r.indexOf(descriptor);
                if(i != -1) {
                        r.remove(i);
                }
                return new ArrayList<PeerInfo>(rank(descriptor, r, "children").subList(0, Math.min(VIEW_MAX_SIZE, r.size())));
        }
        
        private boolean isChild(PeerInfo child)
        {
        	for(int i=0; i<children.size(); i++)
        	{
        		if(child.getPeerAddress().getPeerId() == children.get(i).getPeerAddress().getPeerId())
        		{
        			return true;
        		}
        	}
        	return false;
        }
        
        private ArrayList<PeerAddress> convertToPeerAddress(ArrayList<PeerInfo> list)
        {
        	ArrayList<PeerAddress> list2 = new ArrayList<PeerAddress>();
        	for(int i =0; i<list.size();i++)
        	{
        		PeerInfo tmp = list.get(i);
        		list2.add(tmp.getPeerAddress());
        	}
        	return list2;		
        }
        
//        private ArrayList<PeerInfo> convertToPeerInfo(ArrayList<PeerAddress> list)
//        {
//        	ArrayList<PeerInfo> list2 = new ArrayList<PeerInfo>();
//        	for(int i=0; i<list.size();i++)
//        	{
//        		PeerAddress tmp = list.get(i);
//        		ArrayList<PeerAddress> tmpParents = Snapshot.getPeerParents(tmp);
//        		PeerInfo tmpInfo = new PeerInfo(tmp, tmpParents);
//        		tmpInfo.setHopCount(Snapshot.getHopCount(tmp));
//        		tmpInfo.setSecondHopCount(Snapshot.getSecondHopCount(tmp));
//        		list2.add(tmpInfo);
//        	}
//        	
//        	return list2;
//        }
        
        private void incrementAge(ArrayList<PeerInfo> partners)
        {
        	for(int i=0; i<partners.size(); i++)
        	{
        		partners.get(i).incrementAge();
        		//Implementing Age Control
        		if(partners.get(i).getAge()>ageLimit)
        			partners.remove(i);
        	}
        }
        
        private void acquireChildren(ArrayList<PeerInfo> bufferP)
        {
            ArrayList<PeerInfo> temp = (ArrayList<PeerInfo>)merge(children, bufferP);
            temp = (ArrayList<PeerInfo>)merge(temp, tmanPartners);
            temp = (ArrayList<PeerInfo>)merge(temp, cyclonPartners);
            int minSize = Math.min(temp.size(), self.getPeerAddress().getBandwidth());
            temp = (ArrayList<PeerInfo>) rank(self, temp, "children");
            for(int i=0; i<minSize; i++)
            {
            	//This line needs to be modified to account for churn; i.e. set a timeout event
            	if(!isChild(temp.get(i)) && limit>0 && !candidates.contains(temp.get(i).getPeerAddress()))
            	{
            		candidates.add(temp.get(i).getPeerAddress());
            		limit--;
            		PeerInfo newSelf = copyPI(self);
            		TManAcquireParent request = new TManAcquireParent(self.getPeerAddress(), temp.get(i).getPeerAddress(), newSelf);
            		if(eventCounts.get("TManAcquireChild")==null)
            			eventCounts.put("TManAcquireChild", 1);
            		else eventCounts.put("TManAcquireChild", eventCounts.get("TManAcquireChild")+1);
            		trigger(request, networkPort);
            	}
            }
            if(cyclonOnly == false)
            {
	            ArrayList<PeerInfo> newBuffer = removeCandidates(temp);
	            newBuffer = removeChildren(newBuffer);
	            tmanPartners = (ArrayList<PeerInfo>) selectView(self, newBuffer);
	            //Snapshot.updateTManPartners(self.getPeerAddress(), convertToPeerAddress(tmanPartners));
            }
        }
        
        private void updateHopCount()
        {
        	//Should separate 1st/2nd hopcount conditional statements
        	//Update 2nd hopcount locally even if 1st hopcount doesn't change
        	Integer prevHopCount = self.getHopCount();
        	Integer prevSecondHopCount = self.getSecondHopCount();
        	Integer min = 9999;
        	Integer secondmin = 9999;
        	Integer tracker = -1;
        	for(int i=0; i<parents.size(); i++)
        	{
        		if(parents.get(i).getHopCount() != 9999)
        		{
        			if(min>parents.get(i).getHopCount())
        			{
        				min = parents.get(i).getHopCount();
        				tracker = i;
        			}
        		}
        		
        		if(parents.get(i).getPeerAddress().isSource() == true)
        		{
        			tracker = i;
        			min = 0;
        			break;
        		}      		
        	}
        	
        	for(int i=0; i<parents.size(); i++)
        	{
        		if(secondmin>parents.get(i).getHopCount() && tracker != i)
        			secondmin=parents.get(i).getHopCount();
        	}
        	
        	if(min != 9999)
        	{
            	if(prevHopCount != min+1)
            	{
            		if(secondmin == 9999)
            			secondmin--;
            		self.setHopCount(min+1);
            		self.setSecondHopCount(secondmin+1);
        			//Snapshot.setHopCount(self.getPeerAddress(), min+1);
        			//Snapshot.setSecondHopCount(self.getPeerAddress(), secondmin+1);
        			sendHopCountUpdates(false);
        			return;
            	}
        	}
        	else
        	{
        		if(prevHopCount != 9999)
        		{
            		self.setHopCount(9999);
            		self.setSecondHopCount(9999);
        			//Snapshot.setHopCount(self.getPeerAddress(), 9999);
        			//Snapshot.setSecondHopCount(self.getPeerAddress(), 9999);
        			sendHopCountUpdates(false);
        			return;
        		}
        	}
        	
        	if(secondmin == 9999)
        		secondmin--;
        	if(prevSecondHopCount != secondmin+1)
        	{
        		self.setSecondHopCount(secondmin+1);
    			//Snapshot.setSecondHopCount(self.getPeerAddress(), secondmin+1);
    			sendHopCountUpdates(true);
    			return;
        	}
        }
        
        private void sendHopCountUpdates(boolean parentsOnly)
        {
        	for(int i=0; i<parents.size(); i++)
        	{
        		PeerInfo newSelf = copyPI(self);
        		TManUpdateCost notify = new TManUpdateCost(self.getPeerAddress(), parents.get(i).getPeerAddress(), newSelf, false);
        		trigger(notify, networkPort);
        		if(eventCounts.get("TManHopCountChange")==null)
        			eventCounts.put("TManHopCountChange", 1);
        		else eventCounts.put("TManHopCountChange", eventCounts.get("TManHopCountChange")+1);
        	}
        	if(parentsOnly == false)
        	{
	        	for(int i=0; i<children.size(); i++)
	        	{
	        		PeerInfo newSelf = copyPI(self);
	        		TManUpdateCost notify = new TManUpdateCost(self.getPeerAddress(), children.get(i).getPeerAddress(), newSelf, true);
	        		trigger(notify, networkPort);
	        		if(eventCounts.get("TManHopCountChange")==null)
	        			eventCounts.put("TManHopCountChange", 1);
	        		else eventCounts.put("TManHopCountChange", eventCounts.get("TManHopCountChange")+1);
	        	}
        	}
        }
        
        private ArrayList<PeerInfo> removeSelf(ArrayList<PeerInfo> list)
        {
        	for(int i=0; i<list.size(); i++)
        	{
        		if(self.getPeerAddress().getPeerId() == list.get(i).getPeerAddress().getPeerId())
        			list.remove(i);
        	}
        	return list;
        }
        
        private ArrayList<PeerInfo> removeChildren(ArrayList<PeerInfo> list)
        {
        	for(int i=0; i<list.size(); i++)
        	{
        		for(int j=0; j<children.size(); j++)
        		{
	        		if(children.get(j).getPeerAddress().getPeerId() == list.get(i).getPeerAddress().getPeerId())
	        		{
	        			list.remove(i);
	        			if(i != 0)
	        				i--;
	        			break;
	        		}
        		}
        	}
        	return list;
        }
        
        private ArrayList<PeerInfo> removeCandidates(ArrayList<PeerInfo> list)
        {
        	for(int i=0; i<list.size(); i++)
        	{
        		for(int j=0; j<candidates.size(); j++)
        		{
	        		if(candidates.get(j).getPeerId() == list.get(i).getPeerAddress().getPeerId())
	        		{
	        			list.remove(i);
	        			if(i != 0)
	        				i--;
	        			break;
	        		}
        		}
        	}
        	return list;
        }
        
        private void updateParentPreferences(ArrayList<PeerInfo> temp)
        {
        	for(BigInteger key: parentPreferences.keySet())
        	{
        		for(int i=0; i<temp.size(); i++)
        		{
        			if(key == temp.get(i).getPeerAddress().getPeerId())
        			{
        				temp.get(i).setParentPreference(parentPreferences.get(key));
        			}
        		}
        	}
        }
        
        private void removeFromParentPreferences(PeerAddress child)
        {
        	parentPreferences.remove(child.getPeerId());
        }
        
     /*   private void updateChildrenHopCountsLocally()
        {
        	for(int i=0; i<children.size();i++)
        	{
        		if(self.getHopCount() < children.get(i).getHopCount()+1)
        			children.get(i).setHopcount(self.getHopCount()+1);
        	}
        } */
        
        //Necessary to insure that peers send new copies of themselves, so that their attributes are not overridden
        
        private ArrayList<PeerInfo> copyAllPI(ArrayList<PeerInfo> oldList)
        {
        	ArrayList<PeerInfo> newList = new ArrayList<PeerInfo>();
        	for(PeerInfo p : oldList)
        	{
        		PeerInfo newP = copyPI(p);
        		newList.add(newP);
        	}
        	return newList;
        }
        private PeerInfo copyPI(PeerInfo Old)
        {
        	ArrayList<PeerAddress> newParents = new ArrayList<PeerAddress>(Old.getParents());
        	HashMap<PeerAddress, Integer> newParentHCs = new HashMap<PeerAddress, Integer>(Old.getParentHCs());
        	PeerInfo New = new PeerInfo(Old.getPeerAddress(), newParents);
        	New.setParentHCs(newParentHCs);
        	New.setAge(Old.getAge());
        	New.setHopCount(Old.getHopCount());
        	New.setSecondHopCount(Old.getSecondHopCount());
        	return New;
        }
        
        private Float averageTmanAge()
        {
        	Float total = (float) 0;
        	for(PeerInfo p : tmanPartners)
        	{
        		total = total + p.getAge();
        	}
        	if(tmanPartners.size() != 0)
        		return total/tmanPartners.size();
        	else
        		return (float) 0;
        }
        
        private Float averageCyclonAge()
        {
        	Float total = (float) 0;
        	for(PeerInfo p : cyclonPartners)
        	{
        		total = total + p.getAge();
        	}
        	if(cyclonPartners.size() != 0)
        		return total/cyclonPartners.size();
        	else
        		return (float) 0;
        }
        
        private Float averageTmanAccuracy()
        {
        	Float total = (float) 0;
        	for(PeerInfo p : tmanPartners)
        	{
        		if(p.highestParentBW() == highestParentBW(Snapshot.getPeerParents(p.getPeerAddress())))
        			total += 1;
        		if(p.getHopCount() == Snapshot.getHopCount(p.getPeerAddress()))
        			total += 1;
        		if(p.getSecondHopCount() == Snapshot.getSecondHopCount(p.getPeerAddress()))
        			total += 1;
        	}
        	if(tmanPartners.size() != 0)
        		return total/(tmanPartners.size()*3)*100;
        	else
        		return (float) 0;
        }
        
        private Float averageCyclonAccuracy()
        {
        	Float total = (float) 0;
        	for(PeerInfo p : cyclonPartners)
        	{
        		if(p.highestParentBW() == highestParentBW(Snapshot.getPeerParents(p.getPeerAddress())))
        			total += 1;
        		if(p.getHopCount() == Snapshot.getHopCount(p.getPeerAddress()))
        			total += 1;
        		if(p.getSecondHopCount() == Snapshot.getSecondHopCount(p.getPeerAddress()))
        			total += 1;
        	}
        	if(cyclonPartners.size() != 0)
        		return total/(cyclonPartners.size()*3)*100;
        	else
        		return (float) 0;
        }
        
        private Integer highestParentBW(ArrayList<PeerAddress> parents)
        {
        	Integer max = 0;
        	for(PeerAddress parent: parents)
        	{
        		if(parent.getBandwidth()>max)
        			max = parent.getBandwidth();
        	}
        	return max;
        }
        
        private boolean checkForTestVar(ArrayList<PeerInfo> list)
        {
        	for(PeerInfo element: list)
        	{
        		if(element.getTest())
        			return true;
        	}
        	return false;
        }
        
        private void printChoiceDetails(ArrayList<PeerInfo> temp)
        {
        	String str = "I'm " + self.getPeerAddress().getPeerId() + ":" + self.getPeerAddress().getBandwidth() + " HC:" + self.getHopCount() + ":" + self.getSecondHopCount() + ". This is my ranking: [";
            for(int i=0; i<temp.size(); i++)
            {
            	str += temp.get(i).getPeerAddress().getPeerId() + ":" + temp.get(i).getPeerAddress().getBandwidth() + " HC:" + temp.get(i).getHopCount() + ":" + temp.get(i).getSecondHopCount();
            	if(i != temp.size()-1)
            		str += ", ";
            	else
                	str += "] Children: [";
            }
            for(int i=0; i<children.size(); i++)
            {
            	str += children.get(i).getPeerAddress().getPeerId() + ":" + children.get(i).getPeerAddress().getBandwidth() + " HC:" + children.get(i).getHopCount() + ":" + children.get(i).getSecondHopCount();
            	if(i != children.size()-1)
            		str += ", ";
            	else
                	str += "] Parents: [";
            }
            for(int i=0; i<parents.size(); i++)
            {
            	str += parents.get(i).getPeerAddress().getPeerId() + ":" + parents.get(i).getPeerAddress().getBandwidth() + " HC:" + parents.get(i).getHopCount() + ":" + parents.get(i).getSecondHopCount();
            	if(i != parents.size()-1)
            		str += ", ";
            	else
                	str += "]";
            }
            System.out.println(str);
        }

}
